home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2007 December
/
PCWKCD1207B.iso
/
Blogowanie poza sfera
/
Flock 1.0 beta
/
flock-1.0RC3.en-US.win32.exe
/
flock
/
components
/
flockTwitterService.js
< prev
next >
Wrap
Text File
|
2007-10-18
|
46KB
|
1,363 lines
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2007
// http://flock.com
//
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL
const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;
CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;
CU.import("resource:///modules/FlockScheduler.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource://gre/modules/JSON.jsm");
const MODULE_NAME = "Twitter";
const CLASS_NAME = "Flock Twitter Service";
const CLASS_SHORT_NAME = "twitter";
const CLASS_TITLE = "Twitter";
const CLASS_ID = Components.ID("{535BFF20-9154-11DB-B606-0800200C9A66}");
const CONTRACT_ID = "@flock.com/people/twitter;1";
const FLOCK_RICH_DND_CONTRACTID = "@flock.com/rich-dnd-service;1";
const SERVICE_ENABLED_PREF = "flock.service.twitter.enabled";
const FAVICON = "chrome://flock/content/services/twitter/favicon.png";
// From nsIXMLHttpRequest.idl
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const HTTP_CODE_OK = 200;
const HTTP_CODE_FOUND = 302;
// The delay between two refreshes when the sidebar is closed (in seconds)
const REFRESH_INTERVAL = 1800; // seconds == 30 minutes
// The delay between two refreshes when the sidebar is open (in seconds)
const SHORT_INTERVAL = 300; // seconds == 5 minutes
const TWITTER_URL = "http://www.twitter.com/"
// This is a workaround for the 401 errors we're getting when attempting to
// authenticate to Twitter's API. If we get a 401, we'll silently retry up to
// this number of times before giving up and showing the user an error.
const TWITTER_MAX_AUTH_ATTEMPTS = 5;
// Twitter returns friends in pages of 100.
const TWITTER_FRIENDS_PAGE_SIZE = 100;
// Twitter returns messages in pages of 20.
const TWITTER_MESSAGES_PAGE_SIZE = 20;
// Twitter API documentation specifies the maximum length of a status message.
const TWITTER_MAX_STATUS_LENGTH = 140;
const TWITTER_PROPERTIES = "chrome://flock/locale/services/twitter.properties";
var gApi = null;
/*************************************************************************
* Component: flockTwitterService
*************************************************************************/
function flockTwitterService() {
this._init();
FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccount");
FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccounts");
FlockSvcUtils.flockIWebService.addDefaultMethod(this, "logout");
FlockSvcUtils.flockIManageableWebService
.addDefaultMethod(this, "docRepresentsSuccessfulLogin");
FlockSvcUtils.flockIManageableWebService
.addDefaultMethod(this, "getAccountIDFromDocument");
FlockSvcUtils.flockIManageableWebService
.addDefaultMethod(this, "getCredentialsFromForm");
FlockSvcUtils.flockIManageableWebService
.addDefaultMethod(this, "ownsDocument");
FlockSvcUtils.flockIManageableWebService
.addDefaultMethod(this, "ownsLoginForm");
FlockSvcUtils.flockIRichContentDropHandler
.addDefaultMethod(this, "_handleTextareaDrop");
}
/*************************************************************************
* flockTwitterService: XPCOM Component Creation
*************************************************************************/
flockTwitterService.prototype = new FlockXPCOMUtils.genericComponent(
CLASS_NAME,
CLASS_ID,
CONTRACT_ID,
flockTwitterService,
CI.nsIClassInfo.SINGLETON,
[
CI.nsIObserver,
CI.flockIWebService,
CI.flockIManageableWebService,
CI.flockIPollingService,
CI.flockISocialWebService,
CI.flockIRichContentDropHandler
]
);
// FlockXPCOMUtils.genericModule() categories
flockTwitterService.prototype._xpcom_categories = [
{ category: "wsm-startup" },
{ category: "flockWebService", entry: CLASS_SHORT_NAME },
{ category: "flockRichContentHandler", entry: CLASS_SHORT_NAME }
];
/*************************************************************************
* flockTwitterService: Private Data and Functions
*************************************************************************/
// Member variables.
flockTwitterService.prototype._init =
function fts_init() {
FlockSvcUtils.getLogger(this);
this._logger.debug(".init()");
// Determine whether this service has been disabled, and unregister if so.
var prefService = CC["@mozilla.org/preferences-service;1"]
.getService(CI.nsIPrefBranch);
if (prefService.getPrefType(SERVICE_ENABLED_PREF) &&
!prefService.getBoolPref(SERVICE_ENABLED_PREF))
{
this._logger.debug(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
this.deleteCategories();
return;
}
var profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
var evtID = profiler.profileEventStart("twitter-init");
var obs = CC["@mozilla.org/observer-service;1"]
.getService(CI.nsIObserverService);
obs.addObserver(this, "xpcom-shutdown", false);
this._accountClassCtor = flockTwitterAccount;
FlockSvcUtils.getCoop(this);
this._baseUrn = "urn:twitter";
this._serviceUrn = this._baseUrn + ":service";
if (this._coop.Service.exists(this._serviceUrn)) {
this._c_svc = this._coop.get(this._serviceUrn);
} else {
this._c_svc = new this._coop.Service(this._serviceUrn, {
name: CLASS_SHORT_NAME,
desc: CLASS_TITLE
});
}
this._c_svc.serviceId = CONTRACT_ID;
this._c_svc.logoutOption = true;
// Note that using FlockSvcUtils.getWD here adds the "_WebDetective"
// property to the service.
this._c_svc.domains = FlockSvcUtils.getWD(this)
.getString("twitter", "domains", "twitter.com");
this._c_svc.loginURL = FlockSvcUtils.getWD(this)
.getString("twitter",
"userlogin",
"https://twitter.com/login/");
// Initialize API
gApi = new flockTwitterAPI();
// Update auth states
try {
if (this._WebDetective.detectCookies("twitter", "loggedout", null)) {
this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
}
} catch (ex) {
this._logger.error("ERROR updating auth states for Twitter: " + ex);
}
profiler.profileEventEnd(evtID, "");
}
flockTwitterService.prototype._lightPeopleIcon =
function flockTwitterService__lightPeopleIcon() {
this._logger.debug("._lightPeopleIcon()");
var obs = CC["@mozilla.org/observer-service;1"]
.getService(CI.nsIObserverService);
obs.notifyObservers(null, "new-people-notification", null);
}
flockTwitterService.prototype._refreshAccount =
function fts__refreshAccount(aUrn, aListener) {
var inst = this;
var refreshItem = this._coop.get(aUrn);
var lastUpdate = refreshItem.lastUpdateDate;
var numberOfAnswers = 0;
// Ensures that both listeners have completed before sending our caller a
// response. Also sets the next refresh time to be soon if the people
// sidebar is open.
function onIndividualSuccess() {
numberOfAnswers++;
if (numberOfAnswers >= 3) {
if (inst._acUtils.isPeopleSidebarOpen()) {
refreshItem.nextRefresh = new Date(Date.now() + SHORT_INTERVAL * 1000);
}
if (aListener) {
aListener.onResult();
}
}
}
/**
* {
* "status": {
* "created_at": "Thu May 17 19:19:39 +0000 2007",
* "text": "attending the full staff meeting",
* "id": 67511202
* },
* "url": null,
* "followers_count": 0,
* "friends_count": 0,
* "profile_background_color": "9ae4e8",
* "name": "Matthew Willis",
* "favourites_count": 0,
* "profile_text_color": "000000",
* "statuses_count": 1,
* "profile_link_color": "0000ff",
* "description": null,
* "profile_sidebar_fill_color": "e0ff92",
* "location": null,
* "profile_image_url": "http:\/\/assets0.twitter.com\/images\/default_image.gif?1189634879",
* "id": 6117302,
* "utc_offset": -14400,
* "profile_sidebar_border_color": "87bc44",
* "screen_name": "lilmatt",
* "protected": false
* }
*/
// This listener handles getting the account owner's information.
var userShowListener = {
onSuccess: function userShow_onSuccess(aResult) {
inst._logger.debug("Success for userShow");
if (aResult.name) {
inst._logger.debug("userShow: name: " + aResult.name);
refreshItem.name = aResult.name;
}
if (aResult.screen_name) {
inst._logger.debug("userShow: screen_name: " + aResult.screen_name);
refreshItem.screenName = aResult.screen_name;
refreshItem.URL = inst._WebDetective
.getString(CLASS_SHORT_NAME, "userprofile", null)
.replace("%accountid%", aResult.screen_name);
}
if (aResult.profile_image_url) {
inst._logger.debug("userShow: avatar: " + aResult.profile_image_url);
// If avatar returned is the default image, set coop.Account.avatar
// to null and let the people sidebar code set the Flock common image.
if (inst._hasDefaultAvatar(aResult.profile_image_url)) {
inst._logger.debug("No avatar for account. Setting to null.");
refreshItem.avatar = null;
} else {
refreshItem.avatar = aResult.profile_image_url;
}
}
if (aResult.status &&
aResult.status.text &&
aResult.status.text != refreshItem.statusMessage)
{
inst._logger.debug("userShow: status: " + aResult.status.text);
refreshItem.statusMessage = aResult.status.text;
var dateString = aResult.status.created_at;
refreshItem.lastProfileUpdate = inst._parseTwitterDate(dateString);
}
onIndividualSuccess();
},
onError: function userShow_onError(aError) {
inst._logger.error("Error on userShow");
aListener.onError(aError);
}
};
// This listener handles getting the user's friends' information.
var friendsListener = {
onSuccess: function friends_onSuccess(aResult) {
inst._logger.debug("friendsListener onSuccess");
// Now that we have all the results we need, update the RDF.
function myWorker(aShouldYield) {
// ADD or update existing people
for (var uid in aResult) {
inst._addPerson(aResult[uid], refreshItem);
if (aShouldYield()) {
yield;
}
}
// REMOVE locally people removed on the server
var localEnum = refreshItem.friendsList.children.enumerate();
while (localEnum.hasMoreElements()) {
var identity = localEnum.getNext();
if (!aResult[identity.accountId]) {
inst._logger.info("Friend " + identity.accountId
+ " has been deleted on the server");
refreshItem.friendsList.children.remove(identity);
identity.destroy();
}
}
onIndividualSuccess();
}
FlockScheduler.schedule(null, 0.05, 10, myWorker);
},
onError: function friends_onError(aError) {
inst._logger.error("friendsListener onError");
aListener.onError(aError);
}
};
// This listener handles getting a count of the user's direct messages.
var messageCountListener = {
onSuccess: function messages_onSuccess(aResult) {
inst._logger.debug("messageCountListener onSuccess");
inst._logger.debug("Message count: " + aResult);
refreshItem.accountMessages = aResult;
// Trigger the people icon highlight if the user has messages
if (aResult > 0) {
inst._lightPeopleIcon();
}
onIndividualSuccess();
},
onError: function messages_onError(aError) {
inst._logger.error("messageCountListener onError");
aListener.onError(aError);
}
};
gApi.userShow(refreshItem.accountId, userShowListener);
gApi.getFriendsStatus(null, friendsListener);
gApi.getTotalMessageCount(messageCountListener);
}
flockTwitterService.prototype._addPerson =
function fts__addPerson(aPerson, aCoopAccount) {
// We include the current accountId in the identity urn to prevent friend
// collisions if multiple service accounts have the same friend.
var identityUrn = this._getIdentityUrn(aCoopAccount.accountId, aPerson.id);
var updating = this._coop.Identity.exists(identityUrn);
var identity;
var lastUpdate = this._parseTwitterDate(aPerson.status.created_at);
this._logger.debug("Adding person: " + aPerson.id + " - " + aPerson.name);
// XXX: Since Twitter only timestamps status changes, we need to add
// property-wise profile comparison here.
var lastUpdateType = "status";
var avatarUrl = null;
if (!this._hasDefaultAvatar(aPerson.profile_image_url)) {
avatarUrl = aPerson.profile_image_url;
}
if (updating) {
// Update data of the coop.Identity object
identity = this._coop.get(identityUrn);
if (lastUpdate > identity.lastUpdate) {
identity.name = aPerson.name;
identity.avatar = avatarUrl;
identity.screenName = aPerson.screen_name;
identity.statusMessage = aPerson.status.text;
identity.lastUpdate = lastUpdate;
identity.lastUpdateType = lastUpdateType;
}
} else {
identity = new this._coop.Identity(
identityUrn,
{
name: aPerson.name,
serviceId: this.contractId,
accountId: aPerson.id,
avatar: avatarUrl,
screenName: aPerson.screen_name,
statusMessage: aPerson.status.text,
lastUpdate: lastUpdate,
lastUpdateType: lastUpdateType
}
);
aCoopAccount.friendsList.children.add(identity);
}
}
flockTwitterService.prototype._getIdentityUrn =
function fts__getIdentityUrn(aAccountId, aUid) {
var prefix = "urn:flock:identity:" + CLASS_SHORT_NAME;
return prefix + aAccountId + ":" + aUid;
}
/**
* Helper function to parse Twitter's date string into seconds since epoch.
* @param aDateString A string formatted as: Wed Jan 31 00:16:35 +0000 2007
* @return The number of seconds since the epoch.
*/
flockTwitterService.prototype._parseTwitterDate =
function fts__parseTwitterDate(aDateString) {
this._logger.debug("_parseTwitterDate: in: " + aDateString);
if (!aDateString) {
return 0;
}
// Date.parse() returns milliseconds since epoch. Divide by 1000 for seconds.
return (Date.parse(aDateString) / 1000);
}
/**
* Helper function to determine if the user has customized their avatar based
* on the passed in URL.
* @param aUrl A string containing the contents of the Twitter user's
* "profile_image_url" property.
* @return true if the user is still using the default avatar, else false
*/
flockTwitterService.prototype._hasDefaultAvatar =
function fts_hasDefaultAvatar(aUrl) {
this._logger.debug("_hasDefaultAvatar(" + aUrl + ")");
var defaultUrl = this._WebDetective.getString("twitter", "noAvatar", "");
return (aUrl.indexOf(defaultUrl) != -1);
}
/*************************************************************************
* flockTwitterService: flockIWebService Implementation
*************************************************************************/
// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
flockTwitterService.prototype.contractId = CONTRACT_ID;
// readonly attribute AString icon;
flockTwitterService.prototype.icon = FAVICON;
// readonly attribute boolean needPassword;
flockTwitterService.prototype.needPassword = true;
// readonly attribute AString shortName;
flockTwitterService.prototype.shortName = CLASS_SHORT_NAME;
// readonly attribute long status;
flockTwitterService.prototype.status = CI.flockIWebService.STATUS_UNKNOWN;
// readonly attribute AString title;
flockTwitterService.prototype.title = CLASS_TITLE;
// readonly attribute AString url;
flockTwitterService.prototype.url = TWITTER_URL;
// readonly attribute AString urn;
flockTwitterService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";
/**
* @see flockIWebService#addAccountById
*/
flockTwitterService.prototype.addAccountById =
function fts_addAccountById(aAccountId, aIsTransient, aListener) {
this._logger.debug(".addAccountById('"
+ aAccountId + "', " + aIsTransient + ", aListener)");
if (!aAccountId) {
aListener.onError();
return;
}
var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
var name = (pw) ? pw.user : aAccountId;
var accountUrn = "urn:flock:" + this.shortName + ":account:" + aAccountId;
var account = new this._coop.Account(
accountUrn, {
name: name,
serviceId: this.contractId,
service: this._c_svc,
accountId: aAccountId,
isPollable: false,
isTransient: aIsTransient,
URL: this._WebDetective.getString(CLASS_SHORT_NAME, "userprofile", null)
.replace("%accountid%", aAccountId),
refreshInterval: REFRESH_INTERVAL,
favicon: FAVICON
});
this._coop.accounts_root.children.add(account);
var friendsListUrn = accountUrn + ":friends";
var friendsList = new this._coop.FriendsList(
friendsListUrn,
{
account: account
});
account.friendsList = friendsList;
// Instantiate account component
var acct = this.getAccount(account.id());
if (aListener) {
aListener.onSuccess(acct, "addAccount");
}
return acct;
}
/**
* @see flockIWebService#removeAccount
*/
flockTwitterService.prototype.removeAccount =
function fts_removeAccount(aUrn) {
this._logger.debug(".removeAccount(" + aUrn + ")");
this._acUtils.removeAccount(aUrn);
}
/*************************************************************************
* flockTwitterService: flockIManageableWebService Implementation
*************************************************************************/
// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);
/**
* void updateAccountStatusFromDocument(in nsIDOMHTMLDocument aDocument);
*
* @see flockIManageableWebService#updateAccountStatusFromDocument
*/
flockTwitterService.prototype.updateAccountStatusFromDocument =
function fts_updateAccountStatusFromDocument(aDocument) {
this._logger.debug(".updateAccountStatusFromDocument(aDocument)");
if (this._WebDetective.detect(this.shortName, "loggedout", aDocument, null) ||
this._WebDetective.detectCookies(this.shortName, "loggedout", null))
{
this._logger.debug("We're logged out!");
this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
} else if (this._WebDetective.detect(this.shortName,
"loggedin",
aDocument,
null))
{
this._logger.debug("We're NOT logged out! Let's try to get some creds!");
var results = FlockSvcUtils.newResults();
if (this._WebDetective.detect(this.shortName,
"accountinfo",
aDocument,
results))
{
var acctId = results.getPropertyAsAString("accountid");
var acctUrn = this._acUtils.getAccountURNById(this.urn, acctId);
var acct = this._coop.get(acctUrn);
if (!acct.isAuthenticated) {
acct.isAuthenticated = true;
}
}
} else {
this._logger.debug("We don't match 'loggedout' or 'logged in'");
}
}
/*************************************************************************
* flockTwitterService: flockISocialWebService implementation
*************************************************************************/
flockTwitterService.prototype.maxStatusLength = TWITTER_MAX_STATUS_LENGTH;
/*************************************************************************
* flockTwitterService: flockIPollingService Implementation
*************************************************************************/
/**
* @see flockIPollingService#refresh
*/
flockTwitterService.prototype.refresh =
function fts_refresh(aUrn, aListener) {
this._logger.debug(".refresh(" + aUrn + ")");
var refreshItem = this._coop.get(aUrn);
if (refreshItem instanceof this._coop.Account) {
this._logger.debug("refreshing an Account");
if (refreshItem.isAuthenticated) {
this._refreshAccount(aUrn, aListener);
} else {
// If the user is not logged in, return a success without
// refreshing anything
aListener.onResult();
}
} else {
this._logger.error("can't refresh " + aUrn + " (unsupported type)");
aListener.onError(null);
}
}
/**************************************************************************
* flockTwitterService: nsIObserver Implementation
**************************************************************************/
flockTwitterService.prototype.observe =
function fts_observe(aSubject, aTopic, aState) {
switch (aTopic) {
case "xpcom-shutdown":
var obs = CC["@mozilla.org/observer-service;1"]
.getService(CI.nsIObserverService);
obs.removeObserver(this, "xpcom-shutdown");
break;
}
}
/**************************************************************************
* flockTwitterService: flockIRichContentDropHandler Implementation
**************************************************************************/
flockTwitterService.prototype.handleDrop =
function fts_handleDrop(aFlavours, aTextarea) {
var inst = this;
var dropCallback = function facebook_dropCallback(aFlav) {
// Get URL from dropped text
var dataObj = {}, len = {};
aFlavours.getTransferData(aFlav, dataObj, len);
var text = dataObj.value.QueryInterface(CI.nsISupportsString).data;
var textParts = text.split(": ");
var url = (textParts.length == 2) ? textParts[1] : text;
// Find position
var caretPos = aTextarea.selectionEnd;
var currentValue = aTextarea.value;
// Add a trailing space so that we don't mangle the url
var nextChar = currentValue.charAt(caretPos);
var trailingSpace = ((nextChar == "") ||
(nextChar == " ") ||
(nextChar == "\n"))
? ""
: " ";
// Put it all together to drop the text into the selection. Note: no
// breadcrumb due to twitter length constraint.
aTextarea.value = currentValue.substring(0, caretPos)
+ url
+ trailingSpace
+ currentValue.substring(caretPos);
};
return this._handleTextareaDrop(CLASS_SHORT_NAME, this._c_svc.domains,
aTextarea, dropCallback);
}
/*************************************************************************
* Component: flockTwitterAPI
*************************************************************************/
function flockTwitterAPI() {
FlockSvcUtils.getLogger(this);
this._logger.init("twitterAPI");
FlockSvcUtils.getACUtils(this);
FlockSvcUtils.getCoop(this);
this._logger.debug("constructor");
}
/*************************************************************************
* flockTwitterAPI: XPCOM Component Creation
*************************************************************************/
// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add this component to the list of constructors passed to
// FlockXPCOMUtils.genericModule().
flockTwitterAPI.prototype = new FlockXPCOMUtils.genericComponent(
CLASS_NAME + " API",
"",
"",
flockTwitterAPI,
0,
[]
);
/*************************************************************************
* flockTwitterAPI: Private data and functions
*************************************************************************/
/**
* Actually do the API call.
* @param aListener
* @param aMethod
* @param aParams
* @param aRequestType "GET" or "POST"
* @param aPostVars Array of JS objects to include in the POST body
* @param aCount A variable to increment when a 401 error occurs before
* trying again. This is handled internally by this
* function. External callers should set this to null.
*/
flockTwitterAPI.prototype._call =
function api__call(aListener, aMethod, aParams, aRequestType, aPostVars,
aCount)
{
var requestType = aRequestType.toUpperCase();
var responseFormat = ".json";
var url = "https://twitter.com/" + aMethod + responseFormat;
var idx = 0;
for (var p in aParams) {
// Only use "?" for the first param. Use "&" after.
url += (idx == 0) ? "?" : "&";
url += p + "=" + aParams[p];
idx++;
}
this._logger.debug("._call() url: " + url);
var req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(CI.nsIXMLHttpRequest)
.QueryInterface(CI.nsIJSXMLHttpRequest);
// Don't pop nsIAuthPrompts if auth fails
req.backgroundRequest = true;
var coopAccountUrn = this._acUtils
.getFirstAuthenticatedAccountForService(CONTRACT_ID);
var coopAccount = this._coop.get(coopAccountUrn);
var passwordUrn = "urn:twitter:service:" + coopAccount.accountId;
var creds = this._acUtils.getPassword(passwordUrn);
req.open(requestType, url, true, creds.user, creds.password);
if (requestType == "POST") {
req.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8");
}
var inst = this;
req.onreadystatechange = function ORSC(aEvent) {
inst._logger.debug("._call() ReadyState: " + req.readyState);
if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
try {
inst._logger.debug("._call() Status: " + req.status);
if (req.status/100 == 2) {
inst._logger.debug("._call() response:\n" + req.responseText);
var s = new CU.Sandbox("about:blank");
var result = CU.evalInSandbox("(" + req.responseText + ")", s);
if (result) {
aListener.onSuccess(result);
} else {
inst._logger.debug("._call() error: no result");
aListener.onError();
}
} else if (req.status == 401) {
// XXX: Workaround for 401 errors we're seeing with Twitter.
// Loop call if necessary up to TWITTER_MAX_AUTH_ATTEMPTS times.
// Increment counter
var count;
if (aCount == null) {
count = 1;
} else {
count = aCount + 1;
}
if (count < TWITTER_MAX_AUTH_ATTEMPTS) {
// Try, try again
inst._logger.debug("._call() Got a 401. Will try "
+ (TWITTER_MAX_AUTH_ATTEMPTS - count)
+ " more time(s).");
inst._call(aListener, aMethod, aParams, aRequestType, aPostVars,
count);
} else {
// Ok. Give up.
inst._logger.debug("._call() HTTP error");
aListener.onError(/*req.status*/);
}
} else {
// HTTP errors
inst._logger.debug("._call() HTTP error");
aListener.onError(/*req.status*/);
}
} catch (ex) {
// XMLHTTPERROR (connection lost)
inst._logger.debug("._call() exception: " + ex);
aListener.onError(/*inst.getHTTPError("9001")*/);
}
}
}
var postBody = "";
if (aPostVars) {
for (var v in aPostVars) {
if (postBody.length) {
postBody += "&";
}
postBody += v + "=" + encodeURIComponent(aPostVars[v]);
}
}
if ((requestType == "POST") && postBody && postBody.length) {
this._logger.debug("._call() postBody: " + postBody);
req.send(postBody);
} else {
req.send(null);
}
}
/**
* Get info (including status) about a user.
* @param aUid Twitter uid of the user to query.
* @param aListener
*/
flockTwitterAPI.prototype.userShow =
function api_userShow(aUid, aListener) {
this._logger.debug(".userShow(" + aUid + ", aListener)");
var url = "users/show/" + aUid;
gApi._call(aListener, url, null, "GET", null, null);
}
/**
* direct_messages
* Returns a list of the 20 most recent direct messages sent to the
* authenticating user. The XML and JSON versions include detailed
* information about the sending and recipient users.
*
* URL: http://twitter.com/direct_messages.format
* Formats: xml, json, rss, atom
* Parameters:
* since Optional. Narrows the resulting list of direct messages to just
* those sent after the specified HTTP-formatted date. The same
* behavior is available by setting the If-Modified-Since parameter
* in your HTTP request.
* Ex: http://twitter.com/direct_messages.atom?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
* since_id Optional. Returns only direct messages with an ID greater than
* (that is, more recent than) the specified ID.
* Ex: http://twitter.com/direct_messages.xml?since_id=12345
* page Optional. Retrieves the 20 next most recent direct messages.
* Ex: http://twitter.com/direct_messages.xml?page=3
*/
flockTwitterAPI.prototype.directMessages =
function api_directMessages(aSince, aSinceId, aPage, aListener) {
this._logger.debug(".directMessages(" + aSince + ", "
+ aSinceId + ", "
+ aPage + ", "
+ "aListener)");
var params = {};
if (aSince) {
params.since = aSince;
}
if (aSinceId) {
params.since_id = aSinceId;
}
if (aPage) {
params.page = aPage;
}
var url = "direct_messages";
gApi._call(aListener, url, params, "GET", null, null);
}
/**
* Get a count of all messages for the authenticated user
* @param aListener
*/
flockTwitterAPI.prototype.getTotalMessageCount =
function api_getTotalMessageCount(aListener) {
this._logger.debug(".getTotalMessageCount(aListener)");
// We start off by asking for page 1 of messages.
var page = 1;
// Message counter
var count = 0;
var inst = this;
var getMessagesListener = {
onSuccess: function L_onSuccess(aResult) {
// If a full page is returned then go grab another
inst._logger.debug("Twitter messages returned: " + aResult.length);
count += aResult.length;
if (aResult.length == TWITTER_MESSAGES_PAGE_SIZE) {
inst._logger.debug("Fetching more messages");
page++;
inst.directMessages(null, null, page, getMessagesListener);
} else {
inst._logger.debug("Fetching messages complete");
aListener.onSuccess(count);
}
},
onError: function L_onError(aError) {
inst._logger.debug(".getMessagesListener() error: " + aError);
}
}
this.directMessages(null, null, page, getMessagesListener);
}
/**
* Get a user's friends' updates.
* @param aUid Twitter uid of the user whose friends to view or null to
* view the currently authenticated user's friends.
* @param aListener
*/
flockTwitterAPI.prototype.getFriendsStatus =
function api_getFriendsStatus(aUid, aListener) {
this._logger.debug(".getFriendsStatus(" + aUid + ", aListener)");
var peopleHash = {};
var inst = this;
var params = {};
// We start off by asking for page 1 of friends.
params.page = 1;
var getFriendsListener = {
onSuccess: function L_onSuccess(aResult) {
for (var i in aResult) {
var id = aResult[i].id;
peopleHash[id] = aResult[i];
// If there is no status item then stub one in
if (!peopleHash[id].status) {
peopleHash[id].status = {
created_at: 0,
text: "",
id: null
}
}
inst._logger.debug("Got Twitter person: " + peopleHash[id].name);
}
// If a full page is returned then go grab another
inst._logger.debug("Twitter friends returned: " + aResult.length);
if (aResult.length == TWITTER_FRIENDS_PAGE_SIZE) {
params.page++;
inst._logger.debug("Fetching more friends, now getting page "
+ params.page);
gApi._call(getFriendsListener, url, params, "GET", null, null);
} else {
inst._logger.debug("Fetching friends complete");
aListener.onSuccess(peopleHash);
}
},
onError: function L_onError(aError) {
inst._logger.debug(".getFriendsStatus() error: " + aError);
}
}
var url = "statuses/friends";
if (aUid) {
url += "/" + aUid;
}
gApi._call(getFriendsListener, url, null, "GET", null, null);
}
/**
* Set the user's status
* @param aStatusMessage a string containing the message to set
* @param aListener
*
* Notes from Twitter API documentation:
* -----------------------------------------------------------------------
* Request must be a POST.
* URL: http://twitter.com/statuses/update.format
* Formats: xml, json.
* Returns the posted status in requested format when successful.
* Parameters:
* status Required The text of your status update.
* Be sure to URL encode as necessary.
* Must not be more than 160 characters and should not
* be more than 140 characters to ensure optimal display
*/
flockTwitterAPI.prototype.setStatus =
function api_setStatus(aStatusMessage, aListener) {
this._logger.debug(".setStatus(" + aStatusMessage + ", aListener)");
// substring() starts at 0 while TWITTER_MAX_STATUS_LENGTH counts from 1.
var message = aStatusMessage.substring(0, (TWITTER_MAX_STATUS_LENGTH - 1));
var postVars = {
"source": "flock", // This value specified by Alex Payne at Twitter.
"status": message
};
var url = "statuses/update";
gApi._call(aListener, url, null, "POST", postVars, null);
}
/*************************************************************************
* Component: flockTwitterAccount
*************************************************************************/
function flockTwitterAccount() {
FlockSvcUtils.getLogger(this);
this._logger.init("twitterAccount");
FlockSvcUtils.getACUtils(this);
FlockSvcUtils.getCoop(this);
// XXX: I should be able to use FlockSvcUtils.getWD() here, but it can
// only be called by the service.
this._WebDetective = CC["@flock.com/web-detective;1"]
.getService(CI.flockIWebDetective);
FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "deactivate");
FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "login");
FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "logout");
FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "remove");
FlockSvcUtils.flockISocialWebServiceAccount.addDefaultMethod(this, "getFriendCount");
var sbs = CC["@mozilla.org/intl/stringbundle;1"]
.getService(CI.nsIStringBundleService);
this._bundle = sbs.createBundle(TWITTER_PROPERTIES);
}
/*************************************************************************
* flockTwitterAccount: XPCOM Component Creation
*************************************************************************/
// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add this component to the list of constructors passed to
// FlockXPCOMUtils.genericModule().
flockTwitterAccount.prototype = new FlockXPCOMUtils.genericComponent(
CLASS_NAME + " Account",
"",
"",
flockTwitterAccount,
0,
[
CI.flockIWebServiceAccount,
CI.flockISocialWebServiceAccount
]
);
/*************************************************************************
* flockTwitterAccount: flockIWebServiceAccount Implementation
*************************************************************************/
// readonly attribute AString urn;
flockTwitterAccount.prototype.urn = "";
// void activate(in flockIListener aListener);
flockTwitterAccount.prototype.activate =
function fta_activate(aListener) {
this._logger.debug(".activate()");
var acctCoopObj = this._coop.get(this.urn);
acctCoopObj.isAuthenticated = true;
acctCoopObj.isPollable = true;
if (aListener) {
aListener.onSuccess(this, "activate");
}
}
// DEFAULT: void deactivate(in flockIListener aListener);
// DEFAULT: void login(in flockIListener aListener);
// DEFAULT: void logout(in flockIListener aListener);
// void keep();
flockTwitterAccount.prototype.keep =
function fta_keep() {
this._logger.debug(".keep()");
this._coop.get(this.urn).isTransient = false;
var urn = this.urn.replace("account:", "service:").replace("flock:", "");
this._acUtils.makeTempPasswordPermanent(urn);
}
// DEFAULT: void remove();
/*************************************************************************
* flockTwitterAccount: flockISocialWebServiceAccount Implementation
*************************************************************************/
// readonly attribute boolean hasFriendActions;
flockTwitterAccount.prototype.hasFriendActions = true;
// readonly attribute boolean isPostLinkSupported;
flockTwitterAccount.prototype.isPostLinkSupported = true;
// readonly attribute boolean isMyMediaFavoritesSupported;
flockTwitterAccount.prototype.isMyMediaFavoritesSupported = false;
// readonly attribute boolean isStatusSupported;
flockTwitterAccount.prototype.isStatusSupported = true;
// readonly attribute boolean isStatusEditable;
flockTwitterAccount.prototype.isStatusEditable = true;
// AString formatStatusForDisplay(in AString aStatusMessage);
flockTwitterAccount.prototype.formatStatusForDisplay =
function fma_formatStatusForDisplay(aStatusMessage) {
this._logger.debug(".formatStatusForDisplay(" + aStatusMessage + ")");
var message = (aStatusMessage) ? aStatusMessage : "";
// Responses from Twitter contain these HTML entities. Order is important!
message = message.replace(/&/g, "&")
.replace(/>/g, ">")
.replace(/</g, "<")
.replace(/"/g, '"');
return message;
}
// AString getProfileURLForFriend(in AString aFriendUrn);
flockTwitterAccount.prototype.getProfileURLForFriend =
function fta_getProfileURLForFriend(aFriendUrn) {
this._logger.debug(".getProfileURLForFriend('" + aFriendUrn + "')");
var url = "";
var coopFriend = this._coop.get(aFriendUrn);
var screenName = coopFriend.screenName;
if (screenName) {
url = this._WebDetective.getString(CLASS_SHORT_NAME, "friendProfile", "")
.replace("%screenName%", screenName);
}
this._logger.debug(".getProfileURLForFriend() url: " + url);
return url;
}
// void setStatus(in AString aStatusMessage, in flockIListener aListener);
flockTwitterAccount.prototype.setStatus =
function fma_setStatus(aStatusMessage, aListener) {
this._logger.debug(".setStatus(" + aStatusMessage + ")");
var inst = this;
var statusListener = {
onSuccess: function L_onSuccess(aResult) {
// If the API call succeeded, also set the coop.Account status.
inst._coop.get(inst.urn).statusMessage = aStatusMessage;
if (aListener) {
aListener.onSuccess(aResult, null);
}
},
onError: function L_onError(aError) {
if (aListener) {
aListener.onError(null, null, aError);
}
}
}
gApi.setStatus(aStatusMessage, statusListener);
}
// AString getEditableStatus();
flockTwitterAccount.prototype.getEditableStatus =
function fma_getEditableStatus() {
this._logger.debug(".getEditableStatus()");
var message = this._coop.get(this.urn).statusMessage;
return this.formatStatusForDisplay(message);
}
// AString getMeNotifications();
flockTwitterAccount.prototype.getMeNotifications =
function fma_getMeNotifications() {
this._logger.debug(".getMeNotifications()");
var noties = [];
var inst = this;
function _addNotie(aType, aCount) {
var stringName = "flock." + CLASS_SHORT_NAME + ".noties." + aType + "."
+ ((parseInt(aCount) <= 0) ? "none" : "some");
inst._logger.debug("aType: " + aType
+ " aCount: " + aCount
+ " name: " + stringName);
noties.push({
class: aType,
tooltip: inst._bundle.GetStringFromName(stringName),
count: aCount,
URL: inst._WebDetective.getString(CLASS_SHORT_NAME, aType + "_URL", "")
});
}
var coopAccount = this._coop.get(this.urn);
_addNotie("meMessages", coopAccount.accountMessages);
return JSON.toString(noties);
}
flockTwitterAccount.prototype.markAllMeNotificationsSeen =
function flockTwitterAccount_markAllMeNotificationsSeen(aType) {
this._logger.debug(".markAllMeNotificationsSeen('" + aType + "')");
var c_acct = this._coop.get(this.urn);
switch (aType) {
case "meMessages":
c_acct.accountMessages = 0;
break;
default:
break;
}
}
// AString getFriendActions(in AString aFriendUrn);
flockTwitterAccount.prototype.getFriendActions =
function fma_getFriendActions(aFriendUrn) {
this._logger.debug(".getFriendActions('" + aFriendUrn + "')");
var actionNames = ["friendMessage",
"friendNudge",
"friendViewProfile"];
var actions = [];
var coopFriend = this._coop.get(aFriendUrn);
if (coopFriend) {
var coopAccount = this._coop.get(this.urn);
for each (var action in actionNames) {
actions.push({
label: this._bundle.GetStringFromName("flock."
+ CLASS_SHORT_NAME
+ ".actions." + action),
class: action,
spec: this._WebDetective.getString(CLASS_SHORT_NAME, action, "")
.replace("%accountid%", coopAccount.accountId)
.replace("%friendid%", coopFriend.accountId)
});
}
}
return JSON.toString(actions);
}
// AString getSharingAction(in AString aFriendUrn,
// in nsITransferable aTransferable);
flockTwitterAccount.prototype.getSharingAction =
function fma_getSharingAction(aFriendUrn, aTransferable) {
this._logger.debug(".getSharingAction('" + aFriendUrn + "')");
var sharingAction = "";
var coopFriend = this._coop.get(aFriendUrn);
if (!coopFriend) {
return sharingAction;
}
var flavours = ["text/x-flock-media",
"text/x-moz-url",
"text/unicode"];
var message = CC[FLOCK_RICH_DND_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getMessageFromTransferable(aTransferable,
flavours.length,
flavours);
if (!message.body) {
return sharingAction;
}
sharingAction = this._WebDetective
.getString(CLASS_SHORT_NAME,
"shareAction_directMessage", "")
.replace("%friendid%", coopFriend.accountId)
.replace("%message%", encodeURIComponent(message.body));
this._logger.debug(".getSharingAction(): " + sharingAction);
return sharingAction;
}
flockTwitterAccount.prototype.getPostLinkAction =
function fta_getPostLinkAction(aTransferable) {
var postLinkAction = "";
var url = "";
if (aTransferable) {
// Something was dropped onto the "Post Link" button: get the URL from the
// transferable
var flavours = ["text/x-flock-media",
"text/x-moz-url",
"text/unicode"];
var message = CC[FLOCK_RICH_DND_CONTRACTID]
.getService(CI.flockIRichDNDService)
.getMessageFromTransferable(aTransferable,
flavours.length,
flavours);
url = message.body;
} else {
// The "Post Link" button was clicked: get the current tab's URL
var win = CC["@mozilla.org/appshell/window-mediator;1"]
.getService(CI.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
if (win) {
url = win.gBrowser.currentURI.spec;
}
}
if (url) {
postLinkAction = this._WebDetective
.getString(CLASS_SHORT_NAME,
"shareAction_publicMessage",
"")
.replace("%message%", encodeURIComponent(url));
}
return postLinkAction;
}
/*************************************************************************
* flockTwitterAccount Private Data and Functions
*************************************************************************/
/*************************************************************************
* XPCOM Support - Module Construction
*************************************************************************/
// Create array of components.
var componentsArray = [flockTwitterService];
// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
componentsArray);